/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *    2011 year
 *    Author: Michael Khromov aka MikeDM (Mike D'Morto)
 *    E-Mail: mikedmorto@gmail.com
 */

#include "dbworker.h"
#include <QDebug>
#include <QApplication>



dbworker::dbworker(const QString dbhost, int dbport, const QString dbsocket,
                   const QString dbname, const QString dbuser,
                   const QString dbpass, QObject *parent){
    // устанавливаем необходимые данные и стартуем поток, смотрите всю магию в
    // переопределенном методе run.
    this->dhost=dbhost;
    this->dsocket=dbsocket;
    this->dname=dbname;
    this->duser=dbuser;
    this->dpass=dbpass;
    this->dport=dbport;
    this->start();
}


void dbworker::run(){
    // собственно тут и живет вся магия работы с базой в отдельном потоке
    // главная магия в том, что объект скульной базой должен быть создан тут и жить тут посему
    // конектимся к базе именно тут.
    emit this->sigPutLog(TH,3,tr("Debug: start thread and create database connection"));

    QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");
    // тут мы проверяем существет ли сокет, дело в том что соединение по сокету более приоритетно
    // если даже будет указан хост то его проигнорируют, посему для соединения по хосту просто укажите
    // в строке инициализации хост а сокет не указывайте
    if(this->dsocket.size()>0){
        // соединение по сокету
        db.setConnectOptions(QString("UNIX_SOCKET=%1").arg(this->dsocket));
    }else{
        // соединение по хосту
        db.setHostName(this->dhost);
        db.setPort(this->dport);
    }
    db.setDatabaseName(this->dname);
    db.setUserName(this->duser);
    db.setPassword(this->dpass);
    if(!db.open()){
        // соединение не удалось, бида бида
        this->sigPutLog(TH,1,tr("Error: failure connect to database"));
        this->lastError=db.lastError().text();
        emit this->sigInit(false);
        QApplication::processEvents();
    }else{
        // все гуд соеденились, докладываем
        this->sigPutLog(TH,3,tr("Debug: success connect to database"));
        emit this->sigInit(true);
        QApplication::processEvents();

    }
    unsigned int curid=0;
    this->stop=false;
    for(;;){
        // ложимся спать
        this->msleep(1);
        QApplication::processEvents();

        // вот оно цикл моей мечты =))) FOOOR!!
        // ну а если серьезно то тут будет делаться вся основная магия с метафункциями
        // сначала о приоритетах:
        // синхронный режим работы более приоритетен, посему очереди с его запросами идут впереди
        // а потом уже асинхронный режим.

        // есть ли флаг остановки
        if(this->stop){
            this->sigPutLog(TH,3,tr("Debug: stop thread"));
            db.close();
            this->terminate();
        }



        if(this->mflist.size()>0){
            // опапа в очереди асинхронных что то есть, нукася поработаем
            MFitem * fitem = mflist.takeFirst();
            this->sigPutLog(TH,3,tr("Debug: detect async query. plugin \"%1\" and metafunction \"%2\"").arg(
                                fitem->pl_name).arg(fitem->mfname));
            // делаем запрос в базу на предмет существования такой метафунции
            QSqlQuery queryF("SELECT `db_aq`.`body`"
                             "FROM `core_plugins` INNER JOIN `db_aq` "
                             "ON `core_plugins`.`id` = `db_aq`.`id_plugin`"
                             "AND `core_plugins`.`name` = '"+fitem->pl_name+"'"
                             "AND `db_aq`.`qname` = '"+fitem->mfname+"'");
            // запрос запрос запрос запрос -)) на манер крикушек стадиона
            if(!queryF.exec()){
                // запрос обломался
                this->sigPutLog(TH,3,tr("Debug: sql query error \"%1\"").arg(
                                     queryF.lastError().text()));
                emit this->sigError(fitem->pl_name,queryF.lastError().text());
                // собственно дальше продолжать нет смысла, уничтожаем итем ибо нужно следить за расходом памяти
                // и да запомни падава, тот кто не следит за указателями тому не стать джедаем и быть вечно падаваном.
                delete fitem;
            }else{
                // запрос прошел, смотрим сколько вернулось строк, ежели не одна, значит непорядок, генерим бяку
                if(queryF.size()!=1){
                    // бяка
                    this->sigPutLog(TH,3,tr("Debug: error, incorrect number of rows"));
                    emit this->sigError(fitem->pl_name,tr("incorrect number of rows"));
                }else{
                    // все отлично, извлекаем тело метафункции
                    queryF.next();
                    QString body = queryF.value(0).toString();
                    if(body.size()==0){
                        // косяк то какой тело пустое. фуфуфу низя дальше работать. ругаемся.
                        this->sigPutLog(TH,3,tr("Debug: error, body of metafunction is null"));
                        emit this->sigError(fitem->pl_name,tr("body of metafunction is null"));
                    }else{
                        // все гуд
                        // берем первый символ, это образец за место которых в теле надо будет проставить полученные
                        // параметры
                        QString znak=body.mid(0,1);
                        // получаем тело метафункции для подстановки без первого символа
                        QString telo = body.mid(1,body.size()-1);
                        // смотрим количество вхождений якорь символа znak в тело метафункции
                        int znakcount =telo.count(znak);
                        // проверяем количество параметров совпадает ли с найденым количеством в теле
                        if(znakcount != fitem->params.count()){
                            // количество не совпало, выполнение ставится под угрозу. ругаемся:
                            this->sigPutLog(TH,3,tr("Debug: error, number of parameters did not coincide with the number of variables"));
                            emit this->sigError(fitem->pl_name,tr("number of parameters did not coincide with the number of variables"));
                        }else{
                            // все гуд. продолжаем. тут необходимо подставить параметры на свои места в тело
                            int i = 0;
                            int j = 0;
                            while((j=telo.indexOf(znak,j))!=-1){
                                telo.replace(j,1,sqlsafe(fitem->params.at(i)));
                                ++j;
                                i++;
                            }
                            // покажем в отладку состояние тела, на этот момент это полноценный запрос
                            this->sigPutLog(TH,3,tr("Debug: query = \"%1\"").arg(telo));
                            // тело подготовлено к запросу. Do estu demando!
                            QSqlQuery * queryT = new QSqlQuery(telo);
                            if(!queryT->exec()){
                                // fek! опять какая то ерунда с запросом. админы баз данных часто будут матерится на эту проверку ггг
                                this->sigPutLog(TH,3,tr("Debug: error query from mfunction. error text \"%1\"").arg(
                                                    queryT->lastError().text()));
                                emit this->sigError(fitem->pl_name,queryT->lastError().text());
                            }else{
                                // ну запрос выполнился, теперь нужно его грамотно отдать наружу.
                                if(curid==100000){curid=0;} // защита от переполнения. тонкое место по количеству существующих
                                                            // одновременно результатов. но 100000 должно хватить =))))
                                curid++;
                                data = new libdbmysql_data(queryT);
                                this->resultList.insert(curid,data);
                                // сигналим наружу
                                this->sigPutLog(TH,3,tr("Debug: async data returned"));
                                emit this->sigFinish(fitem->pl_name,curid,data);
                                // вот и вся любофф и титьки набок.
                            }

                        }

                    }

                }
            }


        }
    }
}

void dbworker::freeRes(int id){
    Mdata * tt = this->resultList.take(id);
    delete tt;
    emit this->sigPutLog(TH,3,tr("Debug: free id %1").arg(id));
}

QString dbworker::getLastError(){
    return this->lastError;
}

void dbworker::putMFun(MFitem *queryitem){
    // прилетел еще один асинхронный запрос в базу, ложим в очередь
    this->mflist.append(queryitem);
}

void dbworker::stopT(){
    this->stop=true;
}

QString dbworker::sqlsafe(const QString & str){
    QString aa = str;
    aa.replace("\\","\\\\");
    aa.replace("\"","\\\"");
    aa.replace("'","\\'");
    return aa;
}
